package analyzer

import (
	"database/sql"
	"fmt"
	"log"
	"time"

	"black_list_analyze/lib"
	"black_list_analyze/lib/db/blacklist"
	"black_list_analyze/lib/syslog"

	"black_list_analyze/conf"
)

var (
	LOGS_SIZE int
)

func Analyze() (err error) {
	lastStartTime, err := syslog.LastStartTime()
	if err != nil {
		lib.PackgeErr("Analyze", err)
		return
	}
	log.Println("lastStartTime=", lastStartTime)

	runningLog, err := syslog.Start()
	if err != nil {
		lib.PackgeErr("Analyze", err)
		return
	}
	thisStartTime := runningLog.StartTime
	log.Println("thisStartTime=", thisStartTime)

	log.Println("deleting expire list...")
	if err = blacklist.DeleteExpiredBlackList(thisStartTime); err != nil {
		return err
	}
	if err = blacklist.DeleteExpiredGrayList(thisStartTime); err != nil {
		return err
	}
	if err = blacklist.DeleteExpiredWhiteList(thisStartTime); err != nil {
		return err
	}

	sourceMap, err := getSourceMap()
	if err != nil {
		err = lib.PackgeErr("Analyze", err)
		syslog.End(runningLog.Id, err.Error())
		return
	}
	log.Println("sourceMapSize=", len(sourceMap))

	typeMap, err := getTypeMap()
	if err != nil {
		err = lib.PackgeErr("Analyze", err)
		syslog.End(runningLog.Id, err.Error())
		return
	}
	log.Println("typeMapSize=", len(typeMap))

	log.Println("analyzing black list...")
	err = analyzeBlackList(sourceMap, typeMap, lastStartTime, thisStartTime, LOGS_SIZE)
	if err != nil {
		lib.PackgeErr("Analyze", err)
		syslog.End(runningLog.Id, err.Error())
		return
	}

	log.Println("analyzing gray list...")
	err = analyzeGrayList(sourceMap, typeMap, lastStartTime, thisStartTime, LOGS_SIZE)
	if err != nil {
		lib.PackgeErr("Analyze", err)
		syslog.End(runningLog.Id, err.Error())
		return
	}
	syslog.End(runningLog.Id, "ok")
	return
}

func init() {
	var err error
	LOGS_SIZE, err = conf.Cfg.Int("database.page_size.log")
	lib.CheckFatalErr("[lib/analyzer] init log page size", err)
}

func getTypeMap() (m map[int]blacklist.Id, err error) {
	ids, err := blacklist.GetAllTypeIds()
	if err != nil {
		err = lib.PackgeErr("getTypeMap", err)
		return
	}
	return id2map(ids), nil
}

func getSourceMap() (m map[int]blacklist.Id, err error) {
	ids, err := blacklist.GetAllSourceIds()
	if err != nil {
		err = lib.PackgeErr("getSourceMap", err)
		return
	}
	return id2map(ids), nil
}

func id2map(ids []blacklist.Id) (m map[int]blacklist.Id) {
	m = make(map[int]blacklist.Id)
	for _, id := range ids {
		m[id.Id] = id
	}
	return m
}

func analyzeBlackList(sourceMap, typeMap map[int]blacklist.Id, startDate, endDate string, pageSize int) error {
	err := analyzeList(sourceMap, typeMap, startDate, endDate, pageSize,
		func(startDate, endDate string, index, size int) ([]blacklist.ListLog, error) {
			return blacklist.GetLimitBlackListLogs(startDate, endDate, index, size)
		},
		func(l blacklist.ListLog) error {
			return blacklist.InsertOrUpdateBlackList(l.Id, l.TypeId, l.AppId, calExpireDate(l.ExpireDays))
		})
	return lib.PackgeErr("analyzeBlackList", err)
}

func analyzeGrayList(sourceMap, typeMap map[int]blacklist.Id, startDate, endDate string, pageSize int) error {
	err := analyzeList(sourceMap, typeMap, startDate, endDate, pageSize,
		func(startDate, endDate string, index, size int) ([]blacklist.ListLog, error) {
			return blacklist.GetLimitGrayListLogs(startDate, endDate, index, size)
		},
		func(l blacklist.ListLog) error {
			return blacklist.InsertOrUpdateGrayList(l.Id, l.TypeId, l.AppId, calExpireDate(l.ExpireDays))
		})
	return lib.PackgeErr("analyzeGrayList", err)
}

func analyzeList(sourceMap, typeMap map[int]blacklist.Id, startDate, endDate string, size int,
	getLog func(startDate, endDate string, index, size int) ([]blacklist.ListLog, error),
	insertOrUpdate func(l blacklist.ListLog) error) error {

	index := 0
	logs, err := getLog(startDate, endDate, index, size)
	log.Printf("[debug] begin_date=%s end_date=%s index=%d size=%d logs.size=%d", startDate, endDate, index, size, len(logs))
	for len(logs) > 0 && err == nil {
		log.Printf("[debug] begin_date=%s end_date=%s index=%d size=%d logs.size=%d", startDate, endDate, index, size, len(logs))
		for logIndex, l := range logs {
			if !isValidId(l.SourceId, sourceMap) {
				printLog("is not valid source id, skip", l, err)
				continue
			}
			if !isValidId(l.TypeId, typeMap) {
				printLog("is not valid type id, skip", l, err)
				continue
			}
			if isInWhiteList(l) {
				printLog("already in white list, skip", l, nil)
				continue
			}
			err = insertOrUpdate(l)
			if err != nil {
				return lib.PackgeErr("InsertOrUpdateList", err)
			}
			printLog(fmt.Sprintf("[%d]InsertOrUpdateList", (index+logIndex+1)), l, err)
		}
		index += size
		logs, err = getLog(startDate, endDate, index, size)
	}
	return lib.PackgeErr("analyzeList", err)
}

func isInWhiteList(l blacklist.ListLog) bool {
	flag, err := blacklist.IsExistedInWhiteList(l.Id, l.TypeId, l.AppId)
	if err != nil {
		printLog("isInWiteList", l, err)
		return false
	}
	return flag
}

func isValidId(id int, idMap map[int]blacklist.Id) bool {
	if idMap == nil {
		return false
	}
	_, ok := idMap[id]
	return ok
}

func calExpireDate(days sql.NullInt64) string {
	if !days.Valid {
		return ""
	}
	expireTime := time.Now().Add(time.Hour * 24 * time.Duration(days.Int64))
	return expireTime.Format(lib.FORMAT_DATE)
}

func printLog(prefix string, l blacklist.ListLog, err error) {
	if err != nil {
		log.Printf("[error] %s type_id=%d source_id=%d app_id=%s id=%s err=%s",
			prefix, l.TypeId, l.SourceId, l.AppId, l.Id, err.Error())
		return
	}
	log.Printf("%s type_id=%d source_id=%d app_id=%s id=%s",
		prefix, l.TypeId, l.SourceId, l.AppId, l.Id)
}
